#include "hilo_debugging.h"

DWORD WINAPI hilo_DebugThread( LPVOID lpParam ) {
	HANDLE hThread;				// debugged thread's handle
	CONTEXT cContext;			// debugged thread's context
	hilo_DebugInfo *dbgSetup;	// debug setup information
	BOOL isDebugging;			// boolean value; set to false to exit debug loop
	DEBUG_EVENT de;				// debug event information
	bool handledException;		// true if our debugger handled an exception (our BP as opposed to a natural exception)

	DWORD nextAddress;	// the next address to place a breakpoint at; deprecated
	BOOL  timeoutValue;

	// some setup
	dbgSetup = (hilo_DebugInfo*)lpParam;
	cContext.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; // make more available as necessary

	// register this thread as the active debugger for the target process
	// lpParam needs to contain/point to:
	//	target process HANDLE
	//	callback (python)
	isDebugging = DebugActiveProcess( GetProcessId( dbgSetup->_hProc->hHandle ) );
	if ( isDebugging == NULL ) {
		DEBUG_PRINT( "Failed to debug process %d", GetProcessId( dbgSetup->_hProc->hHandle ) );
		DEBUG_PRINT_VAR( GetLastError() );
		// TODO: report back via callback ???
	} else { // if debugging, set up initial breakpoint
		// dbgSetup->placeBreakpoint( dbgSetup->dwAddress ); // deprecated; see below
		if ( dbgSetup->dwAddress != NULL ) {
			dbgSetup->placeBreakpoint( dbgSetup->dwAddress ); // set up initial breakpoint 
		}

		// signal back to the main thread that setup is complete
		ReleaseSemaphore( dbgSetup->hReadyState, 1, NULL );
	}

	// debugging loop
	while ( isDebugging ) {
		handledException = false;	// by default, unless we find that the exception happened because of a BP of ours
		// wait for a debug event to occur, or check back to see if we should quit
		DEBUG_PRINT("also here");
		while ((timeoutValue = WaitForDebugEvent(&de, DEBUG_THREAD_TIMEOUT) == FALSE)) {
			//DEBUG_PRINT( "Timed out while waiting for a debug event to occur; error, bad flow, or still waiting" );
			if ( WaitForSingleObject( dbgSetup->hReadyState, 0 ) == WAIT_OBJECT_0 ) { // check back
				DEBUG_PRINT( "Discovered unterminated debug thread; leaving" );
				isDebugging = false;
				break;
			}
		}
		DEBUG_PRINT("HERE TOO");

		// check again here to see if we need to break off
		if ( isDebugging == false ) {
			break;
			// TODO: report back to Python
		}

		switch (de.dwDebugEventCode)
		{
			case EXCEPTION_DEBUG_EVENT: 
				switch(de.u.Exception.ExceptionRecord.ExceptionCode)
				{
#pragma region DEPRECATED_EXC_BP
				/* // deprecated; created the remote thread with single stepping */
				// might also consider allowing the creation of an INT3 BP that then "ends" early to allow the TRAP_FLAG case to take over
				//	this implementation would allow a local call to become caught and traced
				case EXCEPTION_BREAKPOINT:
					// open a handle to the thread and attempt to access its context
					hThread = OpenThread( THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION, false, de.dwThreadId );

					if ( hThread == NULL || GetThreadContext( hThread, &cContext ) == NULL ) {
						DEBUG_PRINT( "Failed to retrieve thread/thread context with error code of %d", GetLastError() );
					} else {
						cContext.Eip--; // decrement EIP to fix the address
						// determine if the BP address is what we were waiting for

						DEBUG_PRINT("Attempting to handle INT3 BP @(%08X), expected (%08X)", cContext.Eip, dbgSetup->dwAddress );
						if ( handledException = (cContext.Eip == dbgSetup->dwAddress) ) { // commented out; assuming all int3 BPs encountered are ours
						//if ( handledException = true ) {
							DEBUG_PRINT("HANDLED INT3 BP");
							dbgSetup->restoreInstruction( cContext.Eip );						// restore the instruction
							nextAddress = dbgSetup->performCallback( &de, &cContext );	// perform callback for the next address to BP
																					// value of 0 to quit debugging
							DEBUG_PRINT_VAR( nextAddress );
							if ( isDebugging = (nextAddress != 0) ) {				// if debugging
								//dbgSetup->placeBreakpoint( nextAddress );			// deprecated: place the next breakpoint
								cContext.EFlags |= 1 << 8;							// turn on TRAP_FLAG
							}
							// restore context
							if ( SetThreadContext( hThread, &cContext ) == NULL ) {
								DEBUG_PRINT("Critical fail to restore fixed CONTEXT to thread");
							}
						}
					}
					CloseHandle( hThread ); // close the handle (if open)
					break;
#pragma endregion
				case EXCEPTION_SINGLE_STEP:
					// open a handle to the thread and attempt to access its context
					hThread = OpenThread( THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_QUERY_INFORMATION, false, de.dwThreadId );

					if ( hThread == NULL || GetThreadContext( hThread, &cContext ) == NULL ) {
						DEBUG_PRINT("Failed to retrieve thread/thread context with error code of %d", GetLastError() );
					} else {
						// determine if the BP address is what we were waiting for
						handledException = true;
						isDebugging = dbgSetup->performCallback( &de, &cContext );	// perform callback
																					// value of 0 to quit debugging
						DEBUG_PRINT_VAR( isDebugging );
						DEBUG_PRINT_VAR( cContext.EFlags );
						if ( isDebugging == NULL ) { // turn step execution off
							cContext.EFlags &= ~(1 << 8); // turn TRAP_FLAG off
							DEBUG_PRINT( "Turning trap flag off" );
						} else {
							cContext.EFlags |= 1 << 8;
						}

						//CloseHandle( de.u.Exception.ExceptionRecord.ExceptionAddress );
						// restore context
						if ( SetThreadContext( hThread, &cContext ) == NULL ) {
							DEBUG_PRINT("Critical fail to restore fixed CONTEXT to thread");
							isDebugging = 0;
						}
						dbgSetup->dwInstrCounter++;
					}

					CloseHandle( hThread ); // close the handle (if open)
					break;
				}
				break;
		}
		ContinueDebugEvent(de.dwProcessId, de.dwThreadId, handledException ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED);
	}

	DEBUG_PRINT( "Disabling debugging" );
	DebugActiveProcessStop( GetProcessId( dbgSetup->_hProc->hHandle ) );
	delete dbgSetup;
	return 0;
}